package cmd

import (
	"colly/pkg/tool"
	"colly/services"
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-redis/cache/v9"
	"github.com/gocolly/colly/v2"
	"github.com/gocolly/colly/v2/extensions"
	"github.com/hibiken/asynq"
	"github.com/redis/go-redis/v9"
	"github.com/spf13/cobra"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"
)

const (
	TypeOrderInfo   = "order:info"
	TypeImageResize = "image:resize"
)

type OrderPayload struct {
	OrderNo    string `json:"order_no"`
	TemplateID string `json:"template_id"`
}

type OrderInfo struct {
	OrderNo string      `json:"order_no"`
	Source  string      `json:"source"`
	Status  string      `json:"status"`
	Items   []OrderItem `json:"items"`
	Seller  OrderSeller `json:"buyer"`
	Game    []HasMap    `json:"game"`
	Summary []HasMap    `json:"summary"`
	//Log       []OrderLog  `json:"log"`
	CreatedAt string `json:"created_at"`
}

type OrderItem struct {
	Name            string `json:"name"`
	Type            string `json:"type"`
	Qty             string `json:"qty"`
	Amount          string `json:"amount"`
	Price           string `json:"price"`
	ComissionFee    string `json:"comission_fee"`
	ComissionFeeTax string `json:"comission_fee_tax"`
}

type OrderSeller struct {
	Name    string `json:"name"`
	ChatUrl string `json:"chat_url"`
}

type OrderLog struct {
	Date string `json:"date"`
	Text string `json:"text"`
}

type HasMap struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

var (
	email  string
	page   string
	status int
)

func init() {
	orderCmd.PersistentFlags().StringVarP(&email, "email", "e", "", "email")
	orderCmd.PersistentFlags().StringVarP(&page, "page", "P", "", "page")
	orderCmd.PersistentFlags().IntVarP(&status, "status", "s", 0, "status")
	rootCmd.AddCommand(orderCmd)
	orderCmd.AddCommand(orderSyncCmd)
	orderCmd.AddCommand(orderRecvCmd)
}

var orderCmd = &cobra.Command{
	Use:   "order",
	Short: "This is the queue redis command",
	Long:  "This is the queue redis command",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Please Select Command")
	},
}

var orderRecvCmd = &cobra.Command{
	Use:   "recv",
	Short: "This is the order recv command",
	Long:  "This is the order recv command",
	Run: func(cmd *cobra.Command, args []string) {
		srv := asynq.NewServer(
			asynq.RedisClientOpt{Addr: Cfg.Redis.Addr(), Password: Cfg.Redis.Pass, DB: Cfg.Redis.Db},
			asynq.Config{
				Concurrency: 10,
				RetryDelayFunc: func(n int, e error, t *asynq.Task) time.Duration {
					if t.Type() == TypeOrderInfo {
						return 5 * time.Second
					}
					return asynq.DefaultRetryDelayFunc(n, e, t)
				},
				Queues: map[string]int{
					"critical": 6,
					"default":  3,
					"low":      1,
				},
			},
		)
		mux := asynq.NewServeMux()
		mux.HandleFunc(TypeOrderInfo, HandleOrderTask)
		if err := srv.Run(mux); err != nil {
			fmt.Errorf("could not run server: %v", err)
		}
	},
}

var orderSyncCmd = &cobra.Command{
	Use:   "sync",
	Short: "version subcommand show git version info.",
	PreRun: func(cmd *cobra.Command, args []string) {
		if proxyFile == "" {
			proxyFile = "./clash.yaml"
		}
	},
	Run: func(cmd *cobra.Command, args []string) {

		if email == "" {
			fmt.Println("email required")
			return
		}

		client := NewRedisClient()
		defer client.Close()

		rClient := redis.NewClient(&redis.Options{
			Addr:     Cfg.Redis.Addr(),
			DB:       Cfg.Redis.Db,
			Password: Cfg.Redis.Pass,
		})
		defer rClient.Close()
		mCache := cache.New(&cache.Options{
			Redis: rClient,
			//LocalCache: cache.NewTinyLFU(1000, time.Minute),
		})
		ctx := context.TODO()

		payload := make([]OrderPayload, 0)

		url := "https://www.g2g.com/order/sellOrder?status=" + strconv.Itoa(status)
		if page != "" {
			url = url + "&page=" + page
		}
		c := colly.NewCollector(
			colly.MaxDepth(1),
			//colly.Debugger(&debug.LogDebugger{}),
		)
		extensions.RandomUserAgent(c)
		c.Limit(&colly.LimitRule{
			RandomDelay: 500 * time.Millisecond,
			Parallelism: 10,
		})

		//email := "5218128@qq.com"
		cookies := make([]*http.Cookie, 0)
		for _, account := range Cfg.Account {
			if account.Email != email {
				fmt.Println(account.Email)
				continue
			}
			for _, ck := range account.Cookies {
				cookies = append(cookies, &http.Cookie{
					Name:   ck.Name,
					Value:  ck.Value,
					Domain: ck.Domain,
				})
			}
		}
		if len(cookies) < 1 {
			Log.Errorf("Account Not Found")
			return
		}
		c.SetCookies(url, cookies)

		request := c.Clone()

		c.OnRequest(func(request *colly.Request) {
			request.Headers.Set("Accept-Language", "en-US,en;q=0.9")
		})
		resp := services.RandomConn(proxyFile, url)
		defer resp.NetConn.Close()
		sf := tool.NewSnowflake(1)
		Log.Infof("id=%d, path: %s, name=%s, url=%s", sf.NextID(), proxyFile, resp.Proxy.Name(), url)

		c.WithTransport(&http.Transport{
			DialContext: func(ctx context.Context, network, url string) (net.Conn, error) {
				return resp.NetConn, nil
			},
			MaxIdleConns:          100,
			IdleConnTimeout:       5 * time.Second,
			TLSHandshakeTimeout:   3 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		})

		c.OnHTML("div.sales-history__content", func(element *colly.HTMLElement) {
			//html, _ := element.DOM.Html()
			element.ForEach("tr.sales-history__table-row-read", func(i int, element *colly.HTMLElement) {
				orderNo, _ := element.DOM.Find("a.sales-history__product-id").Attr("href")
				orderNo = strings.Replace(orderNo, "https://www.g2g.com/order/sellOrder/order?oid=", "", -1)
				payload = append(payload, OrderPayload{OrderNo: orderNo, TemplateID: "order:id"})
			})
		})

		c.Visit(url)

		if len(payload) < 1 {
			fmt.Errorf("Order List Empty")
			return
		}

		for index, orderPayload := range payload {

			order := OrderInfo{}
			if err := mCache.Get(ctx, "order:"+orderPayload.OrderNo, &order); err == nil {
				fmt.Println("Order " + orderPayload.OrderNo + " Exist")
				continue
			}

			sp := request.Clone()
			uri := "https://www.g2g.com/order/sellOrder/order?oid=" + orderPayload.OrderNo
			uuu := index
			sp.OnHTML(".container", func(ele *colly.HTMLElement) {
				order.OrderNo = orderPayload.OrderNo
				order.Source = "g2g"
				date := ele.DOM.Find("time.trade__date").Text()
				date = strings.TrimSpace(date)
				parsedTime, _ := time.Parse("Jan 2, 2006, 3:04:05 PM", date)
				order.CreatedAt = parsedTime.Format("2006-01-02 15:04:05")

				status := ele.DOM.Find("span.trade__status").Text()
				order.Status = status

				// Items
				ele.ForEach(".sales-history__table-row", func(iii int, element *colly.HTMLElement) {

					pName := element.DOM.Find(".purchase-title").Text()
					pName = strings.TrimSpace(pName)

					pType := element.DOM.Find(".tooltip__content").Text()
					pType = strings.TrimSpace(pType)

					pQty := element.DOM.Find(`.sales-history__table-quantity`).Eq(1).Text()
					pQty = strings.Replace(pQty, ",", "", -1)
					pQty = strings.Replace(pQty, "\t", "", -1)
					pQty = strings.Replace(pQty, "\n", "", -1)

					pAmonunt := element.DOM.Find(`.sales-history__table-service`).Eq(0).Text()
					pAmonunt = strings.TrimSpace(pAmonunt)
					pAmonunt = strings.Replace(pAmonunt, "\t", "", -1)
					pAmonunt = strings.Replace(pAmonunt, "\n", "", -1)
					if pName == "" {
						return
					}
					pName = strings.Replace(pName, "\u003e ", "", -1)

					pPrice := element.DOM.Find(".sales-history__table-price-unit").Text()
					pPrice = strings.Replace(pPrice, "\t", "", -1)
					pPrice = strings.Replace(pPrice, "\n", "", -1)
					pPrice = strings.TrimSpace(pPrice)

					pComissionFee := element.DOM.Find(".sales-history__table-service").Eq(1).Text()
					pComissionFee = strings.Replace(pComissionFee, "\t", "", -1)
					pComissionFee = strings.Replace(pComissionFee, "\n", "", -1)
					pComissionFee = strings.TrimSpace(pComissionFee)

					pComissionFeeTax := element.DOM.Find(".sales-history__table-service").Eq(2).Text()
					pComissionFeeTax = strings.Replace(pComissionFeeTax, "\t", "", -1)
					pComissionFeeTax = strings.Replace(pComissionFeeTax, "\n", "", -1)

					// order
					item := OrderItem{}
					item.Name = pName
					item.Type = pType
					item.Qty = pQty
					item.Amount = pAmonunt
					item.Price = pPrice
					item.ComissionFee = pComissionFee
					item.ComissionFeeTax = pComissionFeeTax
					order.Items = append(order.Items, item)
				})

				trade := ele.DOM.Find(".trade__content-dynamic")
				seller := trade.Find(".seller__title-orders").Text()
				seller = strings.Replace(seller, "Bought by ", "", -1)
				seller = strings.TrimSpace(seller)
				chatUrl := "https://www.g2g.com/" + seller
				// buyer
				order.Seller = OrderSeller{Name: seller, ChatUrl: chatUrl}

				game := make([]HasMap, 0)
				// Game Info
				ele.ForEach("div.trade__content-dynamic li.game-info__list-item", func(_ int, eles *colly.HTMLElement) {
					key := eles.DOM.Find("span.game-info__title").Text()
					key = strings.TrimSpace(key)
					value := eles.DOM.Find("span.game-info__info").Text()
					value = strings.TrimSpace(value)
					game = append(game, HasMap{
						Key:   key,
						Value: value,
					})
				})
				order.Game = game
				// Summary
				summary := make([]HasMap, 0)
				ele.ForEach("div.trade-history__inner li.game-info__list-item", func(_ int, eley *colly.HTMLElement) {
					key := eley.DOM.Find("span.game-info__title").Text()
					key = strings.TrimSpace(key)
					value := eley.DOM.Find("span.game-info__info").Text()
					value = strings.TrimSpace(value)
					summary = append(summary, HasMap{
						Key:   key,
						Value: value,
					})
				})
				order.Summary = summary

				err := mCache.Set(&cache.Item{
					Ctx:   ctx,
					Key:   "order:" + orderPayload.OrderNo,
					Value: order,
					TTL:   time.Minute * 15,
				})
				if err != nil {
					fmt.Errorf("Cache Order %s Failed.", order.OrderNo)
				}

				detail, _ := json.Marshal(order)
				mCache.Set(&cache.Item{
					Ctx:   ctx,
					Key:   "detail:" + orderPayload.OrderNo,
					Value: string(detail),
					TTL:   time.Minute * 15,
				})

				task, err := NewOrderTask(order)
				if err != nil {
					fmt.Errorf("could not create task: %v", err)
				}
				info, err := client.Enqueue(task)
				if err != nil {
					fmt.Errorf("could not enqueue task: %v", err)
				}
				fmt.Printf("id=%02d, id=%s, order_no=%s, status=%s\n", uuu+1, info.ID, order.OrderNo, order.Status)
			})
			sp.Visit(uri)
		}
	},
}

func NewRedisClient() *asynq.Client {
	return asynq.NewClient(asynq.RedisClientOpt{Addr: Cfg.Redis.Addr(), Password: Cfg.Redis.Pass, DB: Cfg.Redis.Db})
}

func NewOrderTask(payload OrderInfo) (*asynq.Task, error) {
	data, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}
	return asynq.NewTask(TypeOrderInfo, data, asynq.MaxRetry(50)), nil
}

func HandleOrderTask(ctx context.Context, t *asynq.Task) error {
	var p OrderInfo
	if err := json.Unmarshal(t.Payload(), &p); err != nil {
		return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
	}
	fmt.Printf(" [*] Successfully Task: %+v", p)
	return nil
	//log.Printf(" [*] Attempting to Send Welcome Email to User %d...", p.UserID)
	//return fmt.Errorf("could not send email to the user") // <-- Return error
}
